package protobuf.codec.json; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Map; import org.apache.commons.codec.binary.Base64; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; import protobuf.codec.AbstractCodec; import protobuf.codec.Codec.Feature; import protobuf.codec.ParseException; import com.google.protobuf.ByteString; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor.JavaType; import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.ExtensionRegistry.ExtensionInfo; import com.google.protobuf.Message; import com.google.protobuf.Message.Builder; /** * Jackson json reader * * @author sijuv * */ public class JacksonJsonReader { public static Message parse(Builder builder, JsonParser parser, ExtensionRegistry extnRegistry, Map<Feature, Object> featureMap) throws IOException { parser.configure(org.codehaus.jackson.JsonParser.Feature.AUTO_CLOSE_SOURCE, (Boolean) featureMap.get(Feature.CLOSE_STREAM)); parser.nextToken(); parseObject(builder, parser, extnRegistry, featureMap); return builder.build(); } private static Builder parseObject(Builder builder, JsonParser parser, ExtensionRegistry extnRegistry, Map<Feature, Object> featureMap) throws IOException { if (!JsonToken.START_OBJECT.equals(parser.getCurrentToken())) { throw new ParseException("Parser should point to a START_OBJECT event"); } Descriptor descriptor = builder.getDescriptorForType(); while (!parser.nextToken().equals(JsonToken.END_OBJECT)) { JsonToken currToken = parser.getCurrentToken(); assert (currToken.equals(JsonToken.FIELD_NAME)); String fieldName = parser.getCurrentName(); fieldName = AbstractCodec.stripFieldName(fieldName, featureMap); fieldName = AbstractCodec.substituteFieldNameForReading(fieldName, featureMap); FieldDescriptor field = null; if (AbstractCodec.isExtensionFieldName(fieldName, featureMap)) { fieldName = JsonCodec.parseExtensionFieldName(fieldName, featureMap); ExtensionInfo extnInfo = extnRegistry.findExtensionByName(fieldName); if (extnInfo == null) { parser.nextToken(); // Move, we are skipping this field if (JsonToken.START_ARRAY.equals(parser.getCurrentToken()) || JsonToken.START_OBJECT.equals(parser.getCurrentToken())) { parser.skipChildren(); } continue; } field = extnInfo.descriptor; } else if (AbstractCodec.isFieldNameUnknownField(fieldName, featureMap)) { parser.nextToken(); if (AbstractCodec.supportUnknownFields(featureMap)) { String unknownFieldsText = parser.getText(); AbstractCodec.mergeUnknownFieldsFromString(builder, extnRegistry, unknownFieldsText); } continue; } else { field = descriptor.findFieldByName(fieldName); } if (field == null) { throw new ParseException("Field cannot be null, processing fieldName " + fieldName); } parser.nextToken(); setFields(builder, field, parser, extnRegistry, featureMap); } return builder; } private static Builder setFields(Builder builder, FieldDescriptor field, JsonParser parser, ExtensionRegistry extnRegistry, Map<Feature, Object> featureMap) throws IOException { Object value = getValue(builder, field, parser, extnRegistry, featureMap); if (value == null) { // What to do in case of null values ? protobuf does not allow null. } else { builder.setField(field, value); } return builder; } private static void handleArray(Builder builder, FieldDescriptor arrayField, JsonParser parser, ExtensionRegistry extnRegistry, Map<Feature, Object> featureMap) throws IOException { while (!JsonToken.END_ARRAY.equals(parser.nextToken())) { JsonToken token = parser.getCurrentToken(); if (JsonToken.START_ARRAY.equals(token)) { // } else { Object value = getValue(builder, arrayField, parser, extnRegistry, featureMap); builder.addRepeatedField(arrayField, value); } } } private static Object getValue(Builder builder, FieldDescriptor field, JsonParser parser, ExtensionRegistry extnRegistry, Map<Feature, Object> featureMap) throws IOException { JsonToken token = parser.getCurrentToken(); Object value = null; switch (token) { case VALUE_STRING: if (JavaType.ENUM.equals(field.getJavaType())) { value = field.getEnumType().findValueByName(parser.getText()); } else if (JavaType.STRING.equals(field.getJavaType())) { value = parser.getText(); } else if (JavaType.BYTE_STRING.equals(field.getJavaType())) { value = ByteString.copyFrom(Base64.decodeBase64(parser.getText())); } else { throw new UnsupportedEncodingException( String.format( "Unsupported java type [%s] for field [%s] for json type VALUE_STRING", field.getJavaType(), field.getName())); } break; case VALUE_TRUE: value = Boolean.TRUE; break; case VALUE_FALSE: value = Boolean.FALSE; break; case VALUE_NUMBER_INT: if (field.getJavaType().equals(JavaType.INT)) { value = parser.getIntValue(); } else if (JavaType.LONG.equals(field.getJavaType())) { value = parser.getLongValue(); } else { throw new UnsupportedEncodingException( String.format( "Unsupported java type [%s] for field [%s] for json type VALUE_NUMBER_INT", field.getJavaType(), field.getName())); } break; case VALUE_NUMBER_FLOAT: if (JavaType.DOUBLE.equals(field.getJavaType())) { value = parser.getDoubleValue(); } else if (JavaType.FLOAT.equals(field.getJavaType())) { value = parser.getFloatValue(); } else { throw new UnsupportedEncodingException( String.format( "Unsupported java type [%s] for field [%s] for json type VALUE_NUMBER_FLOAT", field.getJavaType(), field.getName())); } break; case START_OBJECT: Builder newBuilder = null; if (field.isExtension()) { newBuilder = extnRegistry.findExtensionByName(field.getFullName()).defaultInstance.toBuilder(); } else { newBuilder = builder.newBuilderForField(field); } value = parseObject(newBuilder, parser, extnRegistry, featureMap).build(); break; case START_ARRAY: handleArray(builder, field, parser, extnRegistry, featureMap); break; case VALUE_NULL: // protobuf does not support null, returning null here however. break; default: throw new UnsupportedEncodingException(String.format( "Unsupported token type [%s]", token)); } return value; } }